package cn.coder.easyjson;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import cn.coder.easyjson.stream.JSONReader;
import cn.coder.easyjson.util.ObjectUtils;

public class JSONObject {

	private final Map<String, Object> innerMap;
	private JSONReader reader;

	public JSONObject() {
		this(null, null);
	}

	public JSONObject(JSONReader reader) {
		this(reader, null);
	}

	public JSONObject(Map<?, ?> map) {
		this(null, map);
	}

	private JSONObject(JSONReader r, Map<?, ?> map) {
		if (map != null) {
			this.innerMap = new LinkedHashMap<>(map.size());
			Set<?> keys = map.keySet();
			for (Object key : keys) {
				put(key.toString(), map.get(key));
			}
		} else
			this.innerMap = new LinkedHashMap<>();
		this.reader = r;
	}

	public void put(String name, Object value) {
		this.innerMap.put(name, value);
	}

	@SuppressWarnings("unchecked")
	public <T> T parse(Class<T> clazz) {
		if (Map.class.isAssignableFrom(clazz)) {
			return (T) this.innerMap;
		}
		Set<Field> fields = ObjectUtils.getDeclaredFields(clazz);
		try {
			T t = clazz.newInstance();
			String name;
			for (Field field : fields) {
				name = field.getName();
				if (this.innerMap.containsKey(name)) {
					field.setAccessible(true);
					if (Collection.class.isAssignableFrom(field.getType())) {
						ParameterizedType type = (ParameterizedType) field.getGenericType();
						if (type != null) {
							String target = type.getActualTypeArguments()[0].getTypeName();
							if (target.startsWith("java.util.Map")) {
								field.set(t, getJSONArray(name).parse(Map.class));
							} else {
								field.set(t, getJSONArray(name).parse(Class.forName(target)));
							}
						} else {
							field.set(t, getJSONArray(name).parse(field.getType()));
						}
					} else {
						field.set(t, ObjectUtils.cast(field.getType(), get(name)));
					}
				}
			}
			return t;
		} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
				| ClassNotFoundException e) {
			throw new RuntimeException(e);
		}
	}

	public JSONObject getJSONObject(String key) {
		Object obj = get(key);
		if (obj instanceof JSONObject)
			return (JSONObject) obj;
		throw new RuntimeException("this name of " + key + " not a json object");
	}

	public JSONArray getJSONArray(String key) {
		Object obj = get(key);
		if (obj instanceof JSONArray)
			return (JSONArray) obj;
		throw new RuntimeException("this name of " + key + " not a json array");
	}

	public Object get(String key) {
		Object temp = this.innerMap.get(key);
		if (this.reader != null) {
			temp = this.reader.lazy(temp);
			this.put(key, temp);
		}
		return temp;
	}

	public String getString(String name) {
		Object temp = get(name);
		return temp != null ? temp.toString() : null;
	}

	public Map<String, Object> getInnerMap() {
		return this.innerMap;
	}

	public String toJson() {
		StringBuffer temp = new StringBuffer("{");
		Set<Entry<String, Object>> entries = this.innerMap.entrySet();
		if (entries.size() > 0) {
			for (Entry<String, Object> entry : entries) {
				temp.append("\"");
				temp.append(entry.getKey().replace("\\", "\\\\").replace("\"", "\\\""));
				temp.append("\":");
				ObjectUtils.appendValue(temp, entry.getValue());
				temp.append(",");
			}
			if (temp.length() > 1)
				temp.delete(temp.length() - 1, temp.length());
		}
		temp.append("}");
		return temp.toString();
	}

}
